import java.io.UnsupportedEncodingException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server {
    private static DatagramSocket mSocket;
    private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
    public static CopyOnWriteArrayList<HashMap<String, String>> mapList = new CopyOnWriteArrayList<>();
    public static int dbgLvFlag =0;
    public static String fileString;
    public static void main(String[] args) {
        if(args[0].equals("-d")){
            dbgLvFlag=1;
        }else if(args[0].equals("-dddd")){
            dbgLvFlag=0;
        }else if(args[0].equals("-dd")){
            dbgLvFlag=2;
        }

        fileString=args[1];

        try {

            // new DatagramSocket
            mSocket = new DatagramSocket(53);

            // accept client datagram
            if(Server.dbgLvFlag>=2)
                System.out.println("***************start listening***************");
            while (true) {
                byte[] receiveData = new byte[1024];
                DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                mSocket.receive(receivePacket);// block until receive packet
                HashMap<String, String> dMap = bytes2Map(receiveData);
                if (dMap.get("QR").equals("0")) {
                    if(Server.dbgLvFlag>=1)
                        System.out.println("Receive Qry: " + dMap);
                    dMap.put("CLIADDR", receivePacket.getSocketAddress().toString());
                    mapList.add(dMap);
                    QueryHandleThread QueryHandleThread = new QueryHandleThread(receivePacket, mSocket, dMap);
                    QueryHandleThread.setPriority(4);
                    QueryHandleThread.start();
                } else if (dMap.get("QR").equals("1")) {
                    if(Server.dbgLvFlag>=1)
                        System.out.println("Receive Resp: " + dMap);
                    ResponseHandleThread responseHandleThread = new ResponseHandleThread(receivePacket, mSocket);
                    responseHandleThread.setPriority(4);
                    responseHandleThread.start();
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            mSocket.close();// close socket
        }
    }

    public static HashMap<String, String> bytes2Map(byte[] dnsBytes) throws UnsupportedEncodingException {
        HashMap<String, String> dMap = new HashMap<String, String>();

        // qry head to bin str
        String binBuf = "";
        for (int i = 0; i < 4; i++) {
            binBuf = binBuf + String.format("%8s", Integer.toBinaryString(dnsBytes[i] & 0xFF)).replace(' ', '0');
        }

        dMap.put("ID", binBuf.substring(0, 16));
        dMap.put("QR", binBuf.substring(16, 17));
        dMap.put("OPCODE", binBuf.substring(17, 21));
        dMap.put("AA", binBuf.substring(21, 22));
        dMap.put("TC", binBuf.substring(22, 23));
        dMap.put("RD", binBuf.substring(23, 24));
        dMap.put("RA", binBuf.substring(24, 25));
        dMap.put("Z", binBuf.substring(25, 28));
        dMap.put("RCODE", binBuf.substring(28, 32));

        byte[] qCountBytes = new byte[2];
        qCountBytes[0] = dnsBytes[4];
        qCountBytes[1] = dnsBytes[5];
        int qCount = ((qCountBytes[0] << 8) & 0x0000ff00) | (qCountBytes[1] & 0x000000ff);
        dMap.put("QDCOUNT", String.valueOf(qCount));

        // debug
        // for(int i=4;i<72;i++)

        // qname to char str
        String qryName = "";
        int qryByteIndex = 12;
        while (true) {
            int charDigitNum = Byte.toUnsignedInt(dnsBytes[qryByteIndex]);
            if (charDigitNum == 0) {
                break;
            }
            byte[] needConvertBytes = new byte[256];
            for (int i = 0; i < charDigitNum; i++) {
                needConvertBytes[i] = dnsBytes[qryByteIndex + i + 1];
            }
            String dataConverted = new String(needConvertBytes, "UTF-8");
            qryName = qryName + dataConverted + '.';
            qryByteIndex = qryByteIndex + charDigitNum + 1;
        }
        qryName = qryName.substring(0, qryName.length() - 1);

        dMap.put("QNAME", qryName);
        dMap.put("QNAME_ASS_INDEX", String.valueOf(qryByteIndex + 1));

        // Tail of qry to hex str
        byte[] qryAss = new byte[4];
        for (int i = 0; i < 4; i++) {
            qryAss[i] = dnsBytes[qryByteIndex + i + 1];
        }
        String typeNclass = bytesToHex(qryAss);
        dMap.put("QTYPE", typeNclass.substring(0, 4));
        dMap.put("QCLASS", typeNclass.substring(4));

        return dMap;
    }

    public static String bytesToHex(byte[] bytes) {
        byte[] hexChars = new byte[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars, StandardCharsets.UTF_8);
    }
}